﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Tycho.Runtime;
using System.Threading;
using Tycho.Transactions;
using System.Transactions;
using System.Diagnostics;

namespace TestTransactions {
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class BankAccounts : Window {
        AnyObject Account1, Account2;
        int TransactionsAborted, TransactionsCompleted;
        const int ConcurrentThreads = 100, NumberOfTransactions = 2000;
        Stopwatch timer;

        public BankAccounts () {
            InitializeComponent ();

            //ThreadPool.SetMinThreads (ConcurrentThreads, ConcurrentThreads);

            Account1 = BankAccount.Create (1000);
            Account2 = BankAccount.Create (1000);

            timer = new Stopwatch ();
        }

        void TransferFunds (object nothing) {
            string a1 = "", a2 = "";
            TransactionalObjectManager txManager = TransactionalObjectManager.RunTransaction (() => {
                AnyObject acct1, acct2;
                Random r = new Random ();
                bool swap = r.Next (2) == 1;

                if (swap) {
                    acct1 = Account1;
                    acct2 = Account2;
                } else {
                    acct1 = Account2;
                    acct2 = Account1;
                }

                acct1.TransferTo (acct2, r.Next (500));
//                a1 = Account1.ToString ();
//                a2 = Account2.ToString ();
            });

            lock (this) {
                TransactionsAborted += txManager.Attempt - 1;
                TransactionsCompleted++;
                if (TransactionsCompleted == NumberOfTransactions) {
                    timer.Stop ();
                }
            }

//            System.Diagnostics.Debug.WriteLine ("thread: " + Thread.CurrentThread.GetHashCode () + ", a1: " + a1 + ", a2: " + a2);
        }

        public void RunTests () {
            using (var tx = new TransactionScope ()) {
                Account1.SetAmount (1000);
                Account2.SetAmount (1000);
                tx.Complete ();
            }

            TransactionsAborted = 0;
            TransactionsCompleted = 0;
            timer.Reset ();
            timer.Start ();
            for (int n = 0; n < NumberOfTransactions; n++) {
                ThreadPool.QueueUserWorkItem (TransferFunds);
                //TransferFunds (null);
            }
        }

        private void RunTestsButton_Click (object sender, RoutedEventArgs e) {
            RunTests ();
        }

        private void CheckAccountsButton_Click (object sender, RoutedEventArgs e) {
            bool correct = false;
            int a1 = 0, a2 = 0;
            TransactionalObjectManager.RunTransaction (() => {
                a1 = Account1.GetAmount ();
                a2 = Account2.GetAmount ();
                correct = 2000 == a1 + a2;
            });

            lock (this) {
                bool allFinished = TransactionsCompleted == NumberOfTransactions;

                ResultRectangle.Fill = new SolidColorBrush (allFinished ? (correct ? Colors.Green : Colors.Red) : Colors.Orange);
                TransactionsAbortedLabel.Content = TransactionsAborted;
                TransactionsStartedLabel.Content = TransactionsCompleted;
                AccountDetailsLabel.Content = a1 + ", " + a2 + " in " + timer.Elapsed;
            }
        }
    }

    static class BankAccount {
        static Symbol Amount = Namespaces.Root.GetNamespace ("bank").Get ("amount");

        public static AnyObject Create (int balance) {
            var t = new TransactionalObject (RuntimeModule.CreateStructure (Amount, RuntimeModule.CreateInteger (balance)), new AnyObject [] { Symbols.RuntimeGetProperty });
            t.Transactionalise ();
            return t;
        }

        public static int GetAmount (this AnyObject account) {
            return account.GetProperty (Amount).ExpectValue<int> ();
        }

        public static void SetAmount (this AnyObject account, int amount) {
            account.SetProperty (Amount, RuntimeModule.CreateInteger (amount));
        }

        public static void TransferTo (this AnyObject from, AnyObject to, int amount) {
            if (from.GetAmount () >= amount) {
                from.SetAmount (from.GetAmount () - amount);
                Thread.Sleep (amount);
                to.SetAmount (to.GetAmount () + amount);
            } else {
                System.Diagnostics.Debug.WriteLine ("not enough funds in " + null + " to transfer " + amount);
            }
        }
    }
}
